#!/bin/bash

if [ "$(basename $0)" != "test.sh" ]; then
    function test::run_tests()
    {
        return 0
    }

    function test::register_test()
    {
        return 0
    }

    return 0
fi

if ${__test_sh__:-false}; then
    return
else
    __test_sh__=true
fi

source include.sh

declare -a tests
declare command_output
declare expect_total expect_success
declare errors
declare snapshot

function test::is_manual_test()
{
    [[ "$test" =~ (:manual$) ]]
}

function test::register_test()
{
    tests+=($*)
}

function test::run_cmd()
{
    command_output="$($*)"
}

function test::run_snapshot()
{
    snapshot="$*"
    snapshot="${snapshot//:/_}"
    snapshot="snapshot/${snapshot// /_}"
    if [ ! -e "$snapshot" ]; then
        echo "generate snapshot at $snapshot"
        $* | xxd > $snapshot
    else
        test::run_cmd $*
        result=$(xxd -r $snapshot)
        test::should_be "$result"
    fi
}

function test::should_be()
{
    ((expect_total++))
    local left_md5sum="$(md5sum <<< "$command_output")"
    local right_md5sum="$(md5sum <<< "$1")"
    if [ "$left_md5sum" == "$right_md5sum" ]; then
        printf "$(cctl g).$(cctl 0)"
        ((expect_success++))
    else
        printf "$(cctl r)x$(cctl 0)"
        errors+=("test::should_be ('$command_output' != '$1')")
        if (( ${#command_output} >= 32 )); then
            errors+=("('$left_md5sum' != '$right_md5sum')")
            xxd <<< "$command_output" > /tmp/1
            xxd <<< "$1" > /tmp/2
            errors+=("diff: $(icdiff /tmp/1 /tmp/2)")
        fi
    fi
}

function test::must_be()
{
    if [ "$command_output" != "$1" ]; then
        echo "$(cctl r)test::must_be ('$command_output' != '$1')$(cctl 0)"
        exit 1
    fi
}

function test::should_return()
{
    local retval="$1"
    shift
    ((expect_total++))
    $* 1>/dev/null 2>&1
    local rv=$?
    if [ "$retval" == "-1" ] && [ "$rv" != "0" ]; then
        rv="-1"
    fi
    if [ "$rv" == "$retval" ]; then
        printf "$(cctl g).$(cctl 0)"
        ((expect_success++))
    else
        errors+=("test::should_return $retval ('$*' return $rv)")
        printf "$(cctl r)x$(cctl 0)"
    fi
}

function test::should_return_true()
{
    test::should_return 0 $*
}

function test::should_return_false()
{
    test::should_return -1 $*
}

function test::var_should_be()
{
    ((expect_total++))
    if [ "${!1}" == "$2" ]; then
        printf "$(cctl g).$(cctl 0)"
        ((expect_success++))
    else
        printf "$(cctl r)x$(cctl 0)"
        errors+=("test::var_should_be ('\$$1'{${!1}} != '$2')")
    fi
}

include "arguments.sh"

function test::run_tests()
{
    function test::run_tests::before1()
    {
        if test::is_manual_test; then
            echo "    running test $(cctl c)$test$(cctl 0)"
            return
        fi
        echo "    running test $(cctl c)$test$(cctl 0) {"
        printf "        "

        ((test_total++))
        expect_total=0
        expect_success=0
        errors=()
    }

    function test::run_tests::before2()
    {
        $debug && set -x
    }

    function test::run_tests::after2()
    {
        set +x
    }

    function test::run_tests::after1()
    {
        test::is_manual_test && return
        printf "\n"
        for (( i = 0; i < ${#errors[*]}; i++ )); do
            echo -e "        $(cctl y)${errors[$i]}$(cctl 0)"
        done
        printf "    } "

        if [ "$expect_total" = "$expect_success" ]; then
            ((test_success++))
            printf "$(cctl g)"
        else
            printf "$(cctl r)"
        fi
        echo "($expect_success/$expect_total)$(cctl 0)"
    }

    function test::run_tests::run()
    {
        for test in ${tests[*]}; do
            if test::is_manual_test; then
                if [ "${#allows[*]}" == "0" ] || [ "$manual" == "false" ]; then
                    continue
                fi
            fi

            if [ "${#allows[*]}" != "0" ]; then
                local can=false
                for allow in ${allows[*]}; do
                    if [[ "$test" =~ $allow ]]; then
                        can=true
                        break
                    fi
                done
                if [ "$can" != "true" ]; then
                    echo "    skip test $(cctl c)$test$(cctl 0)"
                    continue
                fi
            fi

            test::run_tests::before1
            test::run_tests::before2
            $test
            test::run_tests::after2
            test::run_tests::after1
        done
    }

    local debug=false manual=false
    local allows=()
    local test_total test_success

    arguments $*
    while arguments::is_end; do
        if arguments::is_option; then
            case "$(arguments::get_option)" in
                manual)
                    manual=true
                    ;;
                debug)
                    debug=true
                    ;;
                *)
                    echo "not support $(arguments::get)"
                    ;;
            esac
            arguments::next
        else
            allows+=($(arguments::get))
            arguments::next
        fi
    done
    arguments::destroy

    echo "running $((${#manual_test_maps[*]} + ${#tests[*]})) tests"
    echo "{"

    test::run_tests::run

    echo "}"
    echo "running tests finish, ($test_success/$test_total)"
}

for sh in $(ls ~/my/libs); do
    include $sh
done
test::run_tests $*
